<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Object.getOwnPropertyDescriptors()方法</title>
</head>
<body>
  <script>
    /**
     * es5的Object.getOwnPropertyDescriptor()方法会返回某个对象属性的描述对象(descriptor).
     * es6引入了Object.getOwnPropertyDescriptors()方法，返回指定对象所有自身(非继承属性)的描述对象。
    */
   var obj = {
     foo: 123,
     get bar() { return 'abc'}
   };
   var a = Object.getOwnPropertyDescriptors(obj);
   console.log(a);
   // 该方法是实现非常容易
   function getOwnPropertyDescriptors(obj) {
     const result = {};
     for (let key of Reflect.ownKeys(obj)) {//Reflect.ownKeys遍历对象的所有属性包括可枚举的和不可枚举的，Object.keys只能遍历可枚举的。
       result[key] = Object.getOwnPropertyDescriptor(obj, key)
     }
     return result
   }
   // 该方法的引入目的，主要是是为了解决Object.assign()无法正确拷贝set属性和get属性的问题。
  </script>
  <script>
    var source = {
      set foo(value) {
        console.log(value)
      }
    };
    var target1 = {};
    var a = Object.assign(target1, source);
    console.log(a) // {foo: undefined}
    console.log(target1) // {foo: undefined}
    var b = Object.getOwnPropertyDescriptor(target1, 'foo');
    console.log(b);
    // { value: undefined,
    //   writable: true,
    //   enumerable: true,
    //   configurable: true }
    /**
     * 上面代码中，source的对象的foo属性的值是一个赋值函数，Object.assign()方法将这个属性拷贝给target1对象
     * 结果该属性的值变为了undefined。这是因为Object.assign方法总是拷贝一个属性的值，而不会拷贝它背后的赋值方法或者取值方法
     *
     * 这时，Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法，就可以实现正确拷贝。
    */
   var target2 = {};
   Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
   var c = Object.getOwnPropertyDescriptor(target2, 'foo');
   console.log(c)
   // { get: undefined,
   //   set: [Function: set foo],
   //   enumerable: true,
   //   configurable: true }
  </script>
  <script>
    // Object.getOwnPropertyDescriptors()方法的另一个用处，是配合Object.create()方法，将对象属性克隆到一个新对象。这属于浅拷贝。
    var obj = {
      x: 5,
      y: '6'
    };
    var clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
    // Object.create 用于创建一个新对象，使用现有的对象来提供新创建对象的原型对象。
    // Object.create(proto,prop)
    // proto - 必须，新创建对象的原型对象。
    // prop - 可选，设置属性及属性描述，默认值：undefined
    console.log(clone); // {x: 5, y: "6"}
    // 或者
    var shallowClone = (obj) => Object.create(
      Object.getPrototypeOf(obj),
      Object.getOwnPropertyDescriptors(obj)
    )
    console.log(shallowClone(obj)) // {x: 5, y: "6"}
    // 上面代码会克隆对象obj
    //另外，Object.getOwnPropertyDescriptors()方法可以实现一个对象继承另一个对象。以前，继承另一个对象，常常写成下面这样
    var obj = {
      __proto__: prot,
      foo: 123
    };
    // es6规定__proto__只有浏览器要部署，其他环境不用部署。如果去除__proto__，上面的代码就要改成下面这样。
    var obj = Object.create(prot);
    obj.foo = 123;
    // 或者
    var obj = Object.assign(Object.create(prot), {
      foo: 123
    });
    // 有了Object.getOwnPropertyDescriptors()，我们就有了另一种写法。
    var obj = Object.create(prot, Object.getOwnPropertyDescriptors({foo: 123}));
    // Object.getOwnPropertyDescriptors()也可以用来实现 Mixin（混入）模式。
    var mix = (obj) => ({
      with: (...mixins) => mixins.reduce(
        (c, mixin) => Object.create(
          c, Object.getOwnPropertyDescriptors(mixin)
        ), obj
      )
    })
    // multiple mixins example
    var a = {a: 'a'};
    var b = {b: 'b'};
    var c = {c: 'c'};
    var d = mix(c).with(a, b);

    console.log(d.c) // "c"
    console.log(d.b)// "b"
    console.log(d.a) // "a"
    // 上面代码返回一个新对象d，代表了对象a和对象b被混入了对象c的操作。
    // 出于完整性的考虑，Object.getOwnPropertyDescriptors()进入标准以后，以后还会新增Reflect.getOwnPropertyDescriptors()方法。
  </script>
</body>
</html>